#pragma once

#include <string>
#include <vector>
#include <atldef.h>

#include <xmllite.h>
#pragma comment(lib, "xmllite.lib")

using std::wstring;
using std::vector;

class CLiteXMLPhaser
{
//Type
public:
	//Define Operator Mode
	typedef enum _OPMODE {
		READ_MODE = 0,
		WRITE_MODE
	} OPMODE;

	//Define XML Structs
	typedef struct _XMLElements {
		wstring wstrEleName;
		bool bCDATA;
		vector<vector<wstring>*> vecvpwstrEleAttr;
		vector<vector<wstring>*> vecvpwstrEleAttrValue;
		vector<wstring*> vecpwstrEleContents;

		_XMLElements() : wstrEleName(L""), bCDATA(false) {}
		_XMLElements(LPCWSTR lpcwstrElementName, bool isCDATA = false) : wstrEleName(lpcwstrElementName), bCDATA(isCDATA) {}
		
		void AddElement(wstring* lpcwstrContent, vector<wstring>* pvecwstrAttr = NULL, vector<wstring>* pvecwstrAttrValue = NULL)
		{
			ATLASSERT(lpcwstrContent != NULL);

			vecpwstrEleContents.push_back(lpcwstrContent);
			//If no attribute, we push NULL pointer for a place holder
			if(pvecwstrAttr == NULL)
			{
				vecvpwstrEleAttr.push_back(NULL);
				vecvpwstrEleAttrValue.push_back(NULL);
			}
			else
			{
				vecvpwstrEleAttr.push_back(pvecwstrAttr);
				vecvpwstrEleAttrValue.push_back(pvecwstrAttrValue);
			}
		}

	} XMLElements;

	typedef struct _XMLPart {
		wstring wstrPartEleName;
		bool bIsSon;
		void* pSonOrList;
		_XMLPart* pxmlPartSibling;

		_XMLPart() : wstrPartEleName(L""), bIsSon(false), pSonOrList(NULL), pxmlPartSibling(NULL) {}
		_XMLPart(LPCWSTR lpcwstrPartName, bool isHaveSonNode, void* pNode, _XMLPart* pSiblingPointer = NULL)
			: wstrPartEleName(lpcwstrPartName), bIsSon(isHaveSonNode), pSonOrList(pNode), pxmlPartSibling(pSiblingPointer) {}

		void AddSonElementsAndSibling(LPCWSTR lpcwstrPartName, _XMLPart* pPartSon, _XMLPart* pPartSibling)
		{
			wstrPartEleName = lpcwstrPartName;
			bIsSon = true;
			pSonOrList = (void*)pPartSon;
			pxmlPartSibling = pPartSibling;		
		}

		void AddSonElementsNoSibling(LPCWSTR lpcwstrPartName, _XMLPart* pPartSon)
		{
			AddSonElementsAndSibling(lpcwstrPartName, pPartSon, NULL);
		}

		void AddDirectElementsAndSibling(LPCWSTR lpcwstrPartName, XMLElements* pElements, _XMLPart* pPartSibling)
		{
			wstrPartEleName = lpcwstrPartName;
			bIsSon = false;
			pSonOrList = (void*)pElements;
			pxmlPartSibling = pPartSibling;			
		}

		void AddDirectElementsNoSibling(LPCWSTR lpcwstrPartName, XMLElements* pElements)
		{
			AddDirectElementsAndSibling(lpcwstrPartName, pElements, NULL);
		}

	} XMLPart;

	typedef struct _XMLFile {
		XMLPart* pxmlRoot;

		_XMLFile() : pxmlRoot(NULL) {}
		_XMLFile(XMLPart* pRootPart) : pxmlRoot(pRootPart) {}
	} XMLFile;

	//Define Status
	static const BYTE ST_OK				= 0x00;		//Status OK
	static const BYTE ST_ERROR_INIT		= 0x01;		//Init XMLLite Error
	static const BYTE ST_ERROR_FILE		= 0x02;		//File Operation Error
	static const BYTE ST_ERROR_XMLPHASE	= 0x02;		//XML Phase Error

//Method
public:
	CLiteXMLPhaser() : m_wstrXMLFileName(L""), m_pReader(NULL), m_pIStream(NULL) {};
	CLiteXMLPhaser(LPCWSTR lpwszRelativeFilePath, OPMODE opMode);															/* Use the relative path to your program folder */
	~CLiteXMLPhaser();

public:
	BYTE getStatus() { return m_btStatus; }																					/* Get Current Operation Status */
	template <XmlNodeType TType>
	HRESULT getNextTemplate(LPCWSTR lpwszElementPath, LPCWSTR lpwszPropPath, LPWSTR lpwszCDATA, bool bSubCall = false);
	HRESULT getNextCDATA(LPCWSTR lpwszElementPath, LPCWSTR lpwszPropPath, LPWSTR lpwszCDATA, bool bSubCall = false);		/* Make Sure the path is like /root/level1/level2, PropPath with /prop/value, do not surport <tag/> closed like this */
	HRESULT getNextText(LPCWSTR lpwszElementPath, LPCWSTR lpwszPropPath, LPWSTR lpwszCDATA, bool bSubCall = false);			/* Make Sure the path is like /root/level1/level2, PropPath with /prop/value, do not surport <tag/> closed like this */
	HRESULT resetIStream(LPCWSTR lpwszRelativeFilePath = NULL);																/* Reset the input string for another XML file, set NULL to reset current xml stream */

	HRESULT writeElementList(CLiteXMLPhaser::XMLElements* pElements);
	HRESULT writePartList(CLiteXMLPhaser::XMLPart* pXMLParts);
	HRESULT writeXMLFile(CLiteXMLPhaser::XMLFile* pxmlFile);


private:
	void setStatus(BYTE btStatus) { m_btStatus = btStatus; }

//Variable
protected:
	wstring m_wstrXMLFileName;			//XML Filename
	union {
	IXmlReader* m_pReader;
	IXmlWriter* m_pWriter;
	};
	IStream* m_pIStream;				//IO Stream for xml file

private:
	BYTE m_btStatus;
	OPMODE m_opMode;
};


template <XmlNodeType TType>
HRESULT CLiteXMLPhaser::getNextTemplate(LPCWSTR lpwszElementPath, LPCWSTR lpwszPropPath, LPWSTR lpwszCDATA, bool bSubCall)
{
	ATLASSERT(m_pReader != NULL && lpwszElementPath != NULL);

	//Store the Split path
	static vector<wstring> vecElementPath;
	static vector<wstring> vecPropPath;

	//static Member for next call
	static XmlNodeType nodeType;
	static bool bIsElementFound;
	static SHORT sCurrentLevel;
	static SHORT sNextLevel;
	static LPCWSTR lpwszElementName, lpwszProp, lpwszPropValue, lpwszFoundCDATA;

	//Path and set the delimit
	static LPCWSTR lpcwszDelimit = L"/";
	static LPWSTR lpszSplitPos = NULL;
	static WCHAR lpwszSplitString[MAX_PATH];

	//if not Subcall, reinit static member
	if(!bSubCall)
	{
		vecElementPath.clear();
		vecPropPath.clear();

		bIsElementFound = false;
		sCurrentLevel = -1;
		sNextLevel = -1;
		lpwszElementName = lpwszProp = lpwszPropValue = lpwszFoundCDATA = NULL;

		//Reset the Splitting path string
		::wmemset(lpwszSplitString, 0, MAX_PATH);
		::wcscpy(lpwszSplitString, lpwszElementPath);

		//Get the Path of element
		lpszSplitPos = ::wcstok(lpwszSplitString, lpcwszDelimit);
		while(lpszSplitPos != NULL)
		{
			vecElementPath.push_back(wstring(lpszSplitPos));
			lpszSplitPos = ::wcstok(NULL, lpcwszDelimit);
		}

		if(vecElementPath.size() == 0)
		{
			return S_FALSE;
		}

		::wmemset(lpwszSplitString, 0, MAX_PATH);

		//Get the Path of property
		if(lpwszPropPath != NULL)
		{
			::wcscpy(lpwszSplitString, lpwszPropPath);

			lpszSplitPos = ::wcstok(lpwszSplitString, lpcwszDelimit);
			while(lpszSplitPos != NULL)
			{
				vecPropPath.push_back(wstring(lpszSplitPos));
				lpszSplitPos = ::wcstok(NULL, lpcwszDelimit);
			}
		}

		//Reset the position of Node
		resetIStream();
	}

	while(S_OK == m_pReader->Read(&nodeType))
	{
		//The propertys will be walled through in one loop
		//So the prop match level need not be a static
		//We init it on the stack for every element
		SHORT sPropMatch = 0;

		switch(nodeType)
		{
		case XmlNodeType_Element:

			//When found element, we should read that CDATA/TEXT and exit
			//This case happend when the found element has no CDATA/TEXT
			//And this will cause another loop search
			if(bIsElementFound) { bIsElementFound = false; }

			sNextLevel = sCurrentLevel + 1;
			if(sNextLevel == vecElementPath.size())
			{
				//We Need not to deep anymore
				break;
			}

			m_pReader->GetLocalName(&lpwszElementName, NULL);
			if(::wcscmp(vecElementPath[sNextLevel].c_str(), lpwszElementName) == 0)
			{
				sCurrentLevel++;
				if(sCurrentLevel == vecElementPath.size() - 1)
				{
					//The Property should be Property and Value pair
					if(vecPropPath.size() != 0)
					{
						if(m_pReader->MoveToFirstAttribute() == S_FALSE)
						{
							//This matched element has no property
							//Break for the case, no need to check anymore
							break;
						}
						
						do 
						{
							m_pReader->GetLocalName(&lpwszProp, NULL);
							m_pReader->GetValue(&lpwszPropValue, NULL);

							for(vector<wstring>::size_type i=0; i < vecPropPath.size()/2; i++)
							{
								if((::wcscmp(vecPropPath[2*i].c_str(), lpwszProp) == 0) &&
									(::wcscmp(vecPropPath[2*i+1].c_str(), lpwszPropValue) == 0))
								{
									//Matched prop and value, no need to continue check this prop
									//Break the for loop for next prop of the element
									sPropMatch++;
									break;
								}
							}

							if(sPropMatch == vecPropPath.size()/2)
							{
								//All props are match, break the do/while loop
								//And set the found flag
								sPropMatch = 0;
								bIsElementFound = true;
								break;
							}

						} while(m_pReader->MoveToNextAttribute() == S_OK);

						//finish check all prop for this element
						//Reset and go to parent element, whether match or not
						sPropMatch = 0;
						m_pReader->MoveToElement();
					}
					else
					{
						//No Property defined, the text of the element is CDATA/TEXT
						bIsElementFound = true;
					}
				}
			}

			break;

		case XmlNodeType_EndElement:
			
			if(bIsElementFound) { bIsElementFound = false; }

			if(sCurrentLevel == -1)
			{
				//When the top element not found, no need to check here
				break;
			}

			m_pReader->GetLocalName(&lpwszElementName, NULL);
			if(::wcscmp(vecElementPath[sCurrentLevel].c_str(), lpwszElementName) == 0)
			{
				sCurrentLevel--;
			}

			break;

		case TType:
			
			if(bIsElementFound)
			{
				if(FAILED(m_pReader->GetValue(&lpwszFoundCDATA, NULL)))
				{
					lpwszCDATA[0] = L'\0';
					return S_FALSE;
				}
				else
				{
					//Found and Read CDATA/TEXT, reset and exit
					bIsElementFound = false;
					::wcscpy(lpwszCDATA, lpwszFoundCDATA);
					return S_OK;
				}
			}
			break;
		}
	}
	
	//If returned here, means not found
	lpwszCDATA[0] = L'\0';
	return S_FALSE;
}